﻿using System;
using System.Collections.Generic;
using System.Text;

namespace ycdlib
{
	/// <summary>
	/// 解析する
	/// </summary>
	public class YCParser
	{
		static public YCDocument Parse ( string s )
		{
			if( s==null ) throw new ArgumentNullException();
			if( s.Length == 0 ) throw new ArgumentException();

			YCReaderSetting readerSetting = new YCReaderSetting();
			YCDocument __result = null;

			using( CharReader cr = new StringReader( s ) )
			{
				__result = Parse( cr, readerSetting );
				cr.Close();
			}
			return __result;
		}


		static public YCDocument ParseFile ( string filename )
		{
			if( filename==null ) throw new ArgumentNullException();
			if( filename.Length == 0 ) throw new ArgumentException();
			long __len = 0;

			System.IO.FileInfo fi = new System.IO.FileInfo( filename );
			if( !fi.Exists ) return null;

			YCReaderSetting readerSetting = new YCReaderSetting();
			YCDocument __result = null;

			__len = fi.Length;
			if( __len ==0 ) return new YCDocument( readerSetting );


			using( CharReader sr = new FileReader( filename ) )
			{
				__result = Parse( sr, readerSetting );
				sr.Close();
			}
			return __result;
		}


		/// <summary>
		/// 解析する
		/// </summary>
		/// <param name="s">文字配列</param>
		/// <param name="len">文字配列の長さ</param>
		/// <param name="setting">解析設定</param>
		/// <returns></returns>
		static public YCDocument Parse ( CharReader cr, YCReaderSetting setting )
		{
			YCParser P = new YCParser( setting );
			{
				if( P.exec( cr ) )
					return P.doc;
			}

			return P.doc;
		}

		enum ParseStatus
		{
			Unknown = 0,
			TypeName,
			Name,
			Colon,
			Id,
			OpenParenthesis,	//	(
			Value,
			Child,
			CloseParenthesis,	//	)

//			Equal,
//			OpenBrace,		//	{
//			OpenBracket,	//	[
		}


		#region フィールド

		private YCReaderSetting setting;
		private YCDocument doc;
		private Stack<Token> tokenStack;
		private ParseStatus parseStatus;

		private List<YCDParseException> errors;
		private YCNodeAnother rootNode;
		private YCNode currNode;

		#endregion



		/// <summary>
		/// コンストラクタ
		/// </summary>
		/// <param name="setting"></param>
		public YCParser ( YCReaderSetting setting )
		{
			this.setting = setting;
			doc = null;
			errors = null;
			tokenStack = new Stack<Token>();
		}


		/// <summary>
		/// 解析する
		/// </summary>
		/// <param name="s"></param>
		/// <returns></returns>
		private bool exec( CharReader s )
		{
			doc = new YCDocument( setting );
			currNode = rootNode = new YCNodeAnother( "root", null );
			parseStatus = ParseStatus.TypeName;

			_scan( s );

			List<YCNode> children = doc.Children = new List<YCNode>();
			int I = rootNode.ChildCount;
			for( int i=0 ; i<I ; ++i )
			{
				YCNodeBase nd = (YCNodeBase)rootNode[i];
				nd.Parent = null;
				children.Add( nd );
			}
			currNode = null;
			rootNode = null;
			//_parse( tokens );
			return this.errors==null || this.errors.Count==0;
		}


		/// <summary>
		/// 字句解析
		/// </summary>
		/// <param name="cr"></param>
		/// <returns></returns>
		private void _scan ( CharReader cr )
		{
			CharBuffer cb = new CharBuffer();
			CharBuffer.NumberStatus ns = CharBuffer.NumberStatus.FAILURE;

			bool isSlash = false;
			bool isMinus = false;
			uint a = 0;

			cb.Reset();
			do
			{
				int c = cr.Read();	// skipWhitespace( cr );

				//	改行と終端
				if( c<0 || (c >= 0x09 && c <= 0x0d) || c==0x20 || c== 0x2028 || c== 0x2029 )
				{
					if( cb.Length > 0 )
					{
						add( cb, cr );
						isMinus = false;
					}else if( isSlash )
					{
						add( new Token(TokenType.SYMBOL, '/', cr.Line, cr.Col) );
					}
					isSlash = false;
					if( c >= 0 )
						continue;
					break;
				}

				if( isSlash )
				{
					isSlash = false;
					if( c=='/' )
					{
						isMinus = false;
						skipLine( cr );
						continue;
					}
					if( c=='*' )
					{
						isMinus = false;
						skipComment( cr );
						continue;
					}

					add( new Token(TokenType.SYMBOL, '/', cr.Line, cr.Col) );
				}

				if( c=='/' )
				{
					isSlash = true;
					isMinus = false;
					if( cb.Length > 0 )
					{
						a = cb.Test();
						if( (a & 0x0100)!=0 )
						{
							add( cb, cr );
						}
						cb.Reset();
					}
				}else if( c=='"' )
				{
					isSlash = false;
					isMinus = false;
					if( readString( cr, cb ) )
					{
						add( new Token(TokenType.STRING, cb.ToString(), cr.Line, cr.Col) );
						cb.Reset();
					}
				}else if( c=='-' )
				{
					isSlash = false;
					if( isMinus )
					{
						add( new Token(TokenType.SYMBOL, '-', cr.Line, cr.Col) );
					}
					isMinus = true;
					cb.Add( c );
				}else if( c==',' )
				{
					ns = (CharBuffer.NumberStatus)(cb.Test() & 0xff);
					if( ns==CharBuffer.NumberStatus.INTEGER )
					{
						cb.Add( c );
					}else
					{
						add( cb, cr );
						add( new Token(TokenType.SYMBOL, c, cr.Line, cr.Col) );
						cb.Reset();
					}
				}else if( c=='.' )
				{
					ns = (CharBuffer.NumberStatus)(cb.Test() & 0xff);
					if( ns==CharBuffer.NumberStatus.INTEGER )
					{
						cb.Add( c );
					}else
					{
						add( cb, cr );
						add( new Token(TokenType.SYMBOL, c, cr.Line, cr.Col) );
					}

				}else if( c=='+' )
				{
					ns = (CharBuffer.NumberStatus)(cb.Test() & 0xff);
					if( ns == CharBuffer.NumberStatus.EX )
					{
						cb.Add( c );
					}else
					{
						add( cb, cr );
						add( new Token(TokenType.SYMBOL, c, cr.Line, cr.Col) );
					}

				}else if( ('!'<=c && c<'/') || (':'<=c && c<='@') || ('['<=c && c<='`') || ('{'<=c && c<='~') )
				{
					if( cb.Length > 0 )
					{
						add( cb, cr );
					}
					add( new Token( TokenType.SYMBOL, c, cr.Line, cr.Col) );
				}else
				{

					cb.Add( c );
				}

			}while( true );

			return;
		}


		/// <summary>
		/// 構文解析
		/// </summary>
		/// <param name="tokens"></param>
		private void _parse( List<Token> tokens )
		{
			//int I = tokens.Count;
			//for( int i=0 ; i<I ; ++i )
			//{
			//    System.Diagnostics.Debug.WriteLine( tokens[i].ToString() );
			//}
			System.IO.MemoryStream ms = new System.IO.MemoryStream();
			rootNode.Write( ms, 0 );
			System.Diagnostics.Debug.WriteLine( Encoding.UTF8.GetString(ms.GetBuffer()) );
		}


		private void add( CharBuffer cb, CharReader cr )
		{
			Token t = null;
			uint a = cb.Test();
			TokenType tt = TokenType.Unknown;

			CharBuffer.NumberStatus b = (CharBuffer.NumberStatus)(a & 0xff);
			if( b==CharBuffer.NumberStatus.INTEGER  )
			{
				tt = TokenType.INTEGER;

			}else if( b==CharBuffer.NumberStatus.DOT
				|| b==CharBuffer.NumberStatus.DECIMAL )
			{
				tt = TokenType.DECIMAL;

			}else if( b==CharBuffer.NumberStatus.EXNUM)
			{
				tt = TokenType.FLOAT;
			}else
			{
				if( b!=CharBuffer.NumberStatus.FAILURE )
				{
					onError( cr.Line, cr.Col, "invalid number." );
				}

				if( (a & 0x0800)!=0 )
				{
					onError( cr.Line, cr.Col, "not possible to start with a number" );
				}else if( (a & 0x0400)!=0 )
				{
					if( (a & 0x0200)!=0 )
					{
						if( (a & 0x0100)!=0 )
							tt = TokenType.ALPHA;
						else
							tt = TokenType.NUMALPHA;
					}else 
					{
						onError( cr.Line, cr.Col, "detected invalid charactor." );
						tt = TokenType.WITHSYMBOL;
					}
				}else
				{
					tt = TokenType.MLANG;
				}
			}

			t = new Token( tt, cb.ToString(), cr.Line, cr.Col );
			add( t );
			cb.Reset();
		}


		/// <summary>
		/// トークンを追加する
		/// </summary>
		/// <param name="token">追加するトークン</param>
		private void add( Token token )
		{
//System.Diagnostics.Debug.WriteLine( token.TokenType + ":" + token.ToString() );

			switch( parseStatus )
			{
				case ParseStatus.TypeName:
					if( token.IsEqual( '@' ) )
					{
						tokenStack.Push( new Token(TokenType.ALPHA,"",token.Line,token.Col) );
						tokenStack.Push( new Token(TokenType.ALPHA,"",token.Line,token.Col) );
						parseStatus = ParseStatus.OpenParenthesis;
					}else if( token.TokenType == TokenType.ALPHA || token.TokenType == TokenType.NUMALPHA )
					{
						tokenStack.Push( token );
						parseStatus = ParseStatus.Name;
					}else if( token.TokenType==TokenType.MLANG )
					{
						tokenStack.Push( new Token(TokenType.ALPHA,"",token.Line,token.Col) );
						goto __L_FindName;
					}else if( token.IsEqual( ')' ) )
					{
						currNode.Parent.AddChild( currNode );
						currNode = currNode.Parent;
						//parseStatus = ParseStatus.TypeName;
					}else
					{
						onError( token.Line, token.Col, "syntax error. E00001" );
					}
					break;

				case ParseStatus.Name:
					if( token.TokenType >= TokenType.ALPHA )
						goto __L_FindName;

					if( token.IsEqual( '(' ) )
					{
						parseStatus = ParseStatus.Value;
					}else if( token.IsEqual( '#' ) )
					{
						Token t = tokenStack.Peek();
						t.Value = t.Value + "#";
					}else
					{
						onError( token.Line, token.Col, "syntax error. E00003" );
					}
					break;

				case ParseStatus.Colon:
					if( token.IsEqual( '(' ) )
						parseStatus = ParseStatus.Value;

					if( token.IsEqual( ':' ) )
					{
						parseStatus = ParseStatus.Id;
					}else if( token.TokenType>=TokenType.INTEGER )
					{
						onError( token.Line, token.Col, "syntax error. E00004" );
					}
					break;

				case ParseStatus.Id:
					if( token.IsEqual( '(' ) )
					{
						onError( token.Line, token.Col, "syntax error. E00005" );
						parseStatus = ParseStatus.Value;
					}
					
					if( token.TokenType==TokenType.INTEGER || token.TokenType==TokenType.DECIMAL || token.TokenType >= TokenType.ALPHA )
					{
						tokenStack.Push( token );
						parseStatus = ParseStatus.OpenParenthesis;
					}else
					{
						onError( token.Line, token.Col, "syntax error. E00006" );
					}
					break;


				case ParseStatus.OpenParenthesis:
					if( token.IsEqual( '(' ) )
					{
						parseStatus = ParseStatus.Value;
					}else
					{
						onError( token.Line, token.Col, "syntax error. E00007" );
					}
					break;

				case ParseStatus.Value:
					if( token.TokenType==TokenType.INTEGER )
					{
						if( tokenStack.Count > 1 )
						{
							currNode = createNode();
						}else
						{
							currNode = new YCNodeInt( currNode );
							currNode.Name = tokenStack.Pop().Value;
						}
						currNode.Value = token.Value;
						parseStatus = ParseStatus.CloseParenthesis;
					}else if( token.TokenType==TokenType.DECIMAL || token.TokenType==TokenType.FLOAT )
					{
						if( tokenStack.Count > 1 )
						{
							currNode = createNode();
						}else
						{
							currNode = new YCNodeFloat( currNode );
							currNode.Name = tokenStack.Pop().Value;
						}
						currNode.Value = token.Value;
						parseStatus = ParseStatus.CloseParenthesis;
					}else if( token.TokenType==TokenType.STRING )
					{
						if( tokenStack.Count > 1 )
						{
							currNode = createNode();
						}else
						{
							currNode = new YCNodeString( currNode );
							currNode.Name = tokenStack.Pop().Value;
						}
						currNode.Value = token.Value;
						parseStatus = ParseStatus.CloseParenthesis;
					}else if( token.TokenType>=TokenType.ALPHA )
					{
						currNode = createNode();
						parseStatus = ParseStatus.TypeName;
						goto case ParseStatus.TypeName;
					}else if( token.IsEqual( ')' ) )
					{
						currNode = currNode.Parent;
					}else
					{
						onError( token.Line, token.Col, "syntax error. E00008" );
					}
					break;

				case ParseStatus.CloseParenthesis:
					if( token.IsEqual( ')' ) )
					{
						currNode.Parent.AddChild( currNode );
						currNode = currNode.Parent;
						parseStatus = ParseStatus.TypeName;
					}else
					{
						onError( token.Line, token.Col, "syntax error. E00009" );
					}
					break;

				default:
					break;
			}
			goto __L_end;

__L_FindName:
			tokenStack.Push( token );
			parseStatus = ParseStatus.Colon;
			goto __L_end;


__L_end:
			return;
		}


		private YCNode createNode()
		{
			string type = null;
			string name = null;
			string id = null;
			if( tokenStack.Count == 3 )
				id = tokenStack.Pop().Value;

			if( tokenStack.Count == 2 )
				name = tokenStack.Pop().Value;

			if( tokenStack.Count == 1 )
				type = tokenStack.Pop().Value;

			return setting.CreateNode( currNode, type, name, id );
		}


		private bool readString( CharReader cr, CharBuffer cb )
		{
			int c;
		__LOOP1:
			c = cr.Read();
			if( c<0 )		goto __LOOP3;
			if( c=='"' )	return true;
			if( c==0x5c )	goto __LOOP2;
			cb.Add( c );
			goto __LOOP1;

		__LOOP2:
			c = cr.Read();
			if( c<0 ) goto __LOOP3;
			if( c=='"' )	cb.Add( '"' );
			else if( c=='t' ) cb.Add( 9 );
			else if( c=='n' ) cb.Add( 10 );
			else if( c=='r' ) cb.Add( 13 );
			else if( c==0x5c ) cb.Add( 0x5c );
			goto __LOOP1;

		__LOOP3:
			onError( cr.Line, cr.Col, "Invalid EOF in string." );
			return false;
		}


		private void skipComment( CharReader cr )
		{
			int c;
		__LOOP1:
			c = cr.Read();
			if( c<0 )		goto __LOOP3;
			if( c=='*' )	goto __LOOP2;
			goto __LOOP1;

		__LOOP2:
			c = cr.Read();
			if( c<0 )		goto __LOOP3;
			if( c!='/' )	goto __LOOP1;
			return;

		__LOOP3:
			//	エラー：コメントの途中で文字列終了！
			onError( cr.Line, cr.Col, "detect EOF at processing comment." );
		}


		private int skipLine( CharReader cr )
		{
			int c;
		__LOOP:
			c = cr.Read();
			if( c<0 || c==0x0a || c==0x0d || c== 0x2028 || c== 0x2029 ) return c;
			goto __LOOP;
		}


		private int skipWhitespace( CharReader cr )
		{
			int c;
		__LOOP:
			c = cr.Read();
			if( (c >= 0x09 && c <= 0x0d) || c==0x20 || c== 0x2028 || c== 0x2029 )
				goto __LOOP;
			return c;
		}


		private void onError ( int line, int col, string message )
		{
			if( errors == null )
				errors = new List<YCDParseException>();
			errors.Add( new YCDParseException( line, col, message ) );
System.Diagnostics.Debug.WriteLine( "Line " + line + " (" + col +"): " + message );
		}
	}
}
